/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.alipay.sofa.jraft.util;

import java.io.Serializable;
import java.util.Comparator;

import com.alipay.sofa.jraft.util.internal.UnsafeUtf8Util;

/**
 * @author jiachun.fjc
 */
public final class BytesUtil {

    public static final byte[]               EMPTY_BYTES             = new byte[0];

    // A byte array comparator based on lexicograpic ordering.
    private static final ByteArrayComparator BYTES_LEXICO_COMPARATOR = new LexicographicByteArrayComparator();

    public static byte[] nullToEmpty(final byte[] bytes) {
        return bytes == null ? EMPTY_BYTES : bytes;
    }

    public static boolean isEmpty(final byte[] bytes) {
        return bytes == null || bytes.length == 0;
    }

    /**
     * This method has better performance than String#getBytes(Charset),
     * See the benchmark class: Utf8Benchmark for details.
     */
    public static byte[] writeUtf8(final String in) {
        if (in == null) {
            return null;
        }
        // Calculate the encoded length.
        final int len = UnsafeUtf8Util.encodedLength(in);
        final byte[] outBytes = new byte[len];
        UnsafeUtf8Util.encodeUtf8(in, outBytes, 0, len);
        return outBytes;
    }

    /**
     * This method has better performance than String#String(byte[], Charset),
     * See the benchmark class: Utf8Benchmark for details.
     */
    public static String readUtf8(final byte[] in) {
        if (in == null) {
            return null;
        }
        return UnsafeUtf8Util.decodeUtf8(in, 0, in.length);
    }

    public static byte[] nextBytes(final byte[] bytes) {
        Requires.requireNonNull(bytes, "bytes");
        final int len = bytes.length;
        if (len == 0) { // fast path
            return new byte[] { 0 };
        }
        final byte[] nextBytes = new byte[len + 1];
        System.arraycopy(bytes, 0, nextBytes, 0, len);
        nextBytes[len] = 0;
        return nextBytes;
    }

    public static ByteArrayComparator getDefaultByteArrayComparator() {
        return BYTES_LEXICO_COMPARATOR;
    }

    public static int compare(final byte[] a, final byte[] b) {
        return getDefaultByteArrayComparator().compare(a, b);
    }

    public static byte[] max(final byte[] a, final byte[] b) {
        return getDefaultByteArrayComparator().compare(a, b) > 0 ? a : b;
    }

    public static byte[] min(final byte[] a, final byte[] b) {
        return getDefaultByteArrayComparator().compare(a, b) < 0 ? a : b;
    }

    public interface ByteArrayComparator extends Comparator<byte[]>, Serializable {

        int compare(final byte[] buffer1, final int offset1, final int length1, final byte[] buffer2,
                    final int offset2, final int length2);
    }

    private static class LexicographicByteArrayComparator implements ByteArrayComparator {

        private static final long serialVersionUID = -8623342242397267864L;

        @Override
        public int compare(final byte[] buffer1, final byte[] buffer2) {
            return compare(buffer1, 0, buffer1.length, buffer2, 0, buffer2.length);
        }

        public int compare(final byte[] buffer1, final int offset1, final int length1, final byte[] buffer2,
                           final int offset2, final int length2) {
            // short circuit equal case
            if (buffer1 == buffer2 && offset1 == offset2 && length1 == length2) {
                return 0;
            }
            // similar to Arrays.compare() but considers offset and length
            final int end1 = offset1 + length1;
            final int end2 = offset2 + length2;
            for (int i = offset1, j = offset2; i < end1 && j < end2; i++, j++) {
                int a = buffer1[i] & 0xff;
                int b = buffer2[j] & 0xff;
                if (a != b) {
                    return a - b;
                }
            }
            return length1 - length2;
        }
    }

    private BytesUtil() {
    }
}
